home *** CD-ROM | disk | FTP | other *** search
- /**
- * TextEditorLib library class takes over typing functions
- * of a browser that supports javascript1.5, it handles
- * multiple TextNodes at the same time by storing text node references in a map.
- * Thanks to Alex Benenson and Roman Mironenko
- */
- var TextEditorLib = {
-
- // this map will contain all TextNodes.
- textNodeMap : new Array(),
-
- isCapsOn : false,
-
- nodeId : 1,
-
- areAnyTextEditorsAttached : function() {
- if (!TextEditorLib.isObjectEmpty(this.textNodeMap)) {
- for(var i=0;i<TextEditorLib.textNodeMap.length;i++) {
- var txtNode = TextEditorLib.textNodeMap[i];
- if (!TextEditorLib.isObjectEmpty(txtNode)) {
- var attr = txtNode.txtnode.getAttribute("TextEditorLib.attribute.id");
- if (!TextEditorLib.isObjectEmpty(attr)) {
- return true;
- }
-
- }
- }
- }
- return false;
- },
-
- isObjectEmpty : function(obj) {
- var r;
- try {
- r= obj==undefined || obj==null || obj.toString()=="";
- } catch (e) {
- r=true;
- }
- return r;
- },
-
- isNodeEditor : function(node) {
- return node.getEditor != undefined || node.contentDocument != undefined;
- },
-
- toggleEditor : function(func) {
- if (func==undefined || func==null) {
- throw "Translation function undefined.";
- }
- var node = TextEditorLib.getNode();
- if (node==null) {return null;}
- var attr = node.getAttribute("TextEditorLib.attribute.id");
- if (TextEditorLib.isObjectEmpty(attr)) {
- attr=0;
- }
- var textNode = TextEditorLib.textNodeMap[attr];
- if (textNode==undefined || TextEditorLib.isObjectEmpty(attr)) {
- // define new text node
- attr = this.nodeId;
- node.setAttribute("TextEditorLib.attribute.id",attr);
- this.nodeId++;
- textNode = new TextNode.TextNode(node, "keypress", TextEditorLib.onKeyHandler, func, "keyup", TextEditorLib.handleCapsLockPressed, TextEditorLib.isNodeEditor(node));
- textNode.setUp();
- TextEditorLib.textNodeMap[attr]=textNode;
- } else {
- // destroy existing text node
- textNode.tearDown();
- delete TextEditorLib.textNodeMap[attr];
- node.setAttribute("TextEditorLib.attribute.id",null);
- }
- },
-
- getTextNode : function() {
- var node = TextEditorLib.getNode();
- var attr = node.getAttribute("TextEditorLib.attribute.id");
- if (TextEditorLib.isObjectEmpty(attr)) {
- attr=0;
- }
- return TextEditorLib.textNodeMap[attr];
- },
-
- // try to initialize this text node with a focused node from the document.
- getNode : function() {
- var node = document.commandDispatcher.focusedElement;
- if (node==null) {
- if (document.commandDispatcher.focusedWindow) {
- if (document.commandDispatcher.focusedWindow.document) {
- // find ThunderBird compose window (oh, yes, this is riduculous.)
- var editors = document.getElementsByTagName("editor");
- for (var i = 0; i < editors.length; i++) {
- if (editors[i].contentWindow == document.commandDispatcher.focusedWindow) {
- return editors[i];
- }
- }
- }
- node = document.commandDispatcher.focusedWindow.frameElement;
- if (node && node.contentDocument && node.contentDocument.designMode=="on") {
- return node; // midas in iframe.
- }
- }
- } else {
- var name = node.localName.toUpperCase();
- var type = node.type;
- if (name=="TEXTAREA" || name=="TEXTBOX" || (name=="INPUT" && (type=="text" || type=="file"))) {
- if (!node.disabled && !node.readOnly) {
- return node;
- }
- }
- }
- return null;
- },
-
-
- isEnterPressed : function(key) {
- if (key==13) {
- return true;
- }
- return false;
- },
-
- handleCapsLockPressed : function(event) {
- if (event.which==20) {
- if (TextEditorLib.isCapsOn==false) {
- TextEditorLib.isCapsOn=true;
- } else {
- TextEditorLib.isCapsOn=false;
- }
- }
- },
-
- isCapitalLetter : function(isShiftOn) {
- var isCapital=TextEditorLib.isCapsOn;
- if (isShiftOn) {
- if (isCapital) {
- isCapital=false;
- } else {
- isCapital=true;
- }
- }
- return isCapital;
- },
-
- onKeyHandler : function(event) {
- // if this event was already processed by this handler, do not process it again
- if (event.keyCode == 255 && event.charCode > 0) {
- // I can't explain why this needs to be done, but midas will fail
- // to process event otherwise.
- if (event.target.nodeName == "HTML")
- for (var i in event) if (("" + i).toUpperCase()) event[i];
- return;
- }
- if (event.charCode > 0 && !event.ctrlKey && !event.altKey && !event.metaKey) {
- var txtnode = TextEditorLib.getTextNode();
- // is shift on, is typed letter capital?
- var isShiftOn = event.shiftKey;
- var isCapital=TextEditorLib.isCapitalLetter(isShiftOn);
- // translate typed character
- var translator = txtnode.translationFunction;
- var ch = translator(event.which, isCapital, isShiftOn);
- if (ch==-1) {
- return;
- }
- event.preventDefault();
- // in case if the return value is an array, print each element of the array in sequence.
- if (ch instanceof Array) {
- for(var i=0;i<ch.length;i++) {
- var newEvent = document.createEvent("KeyEvents");
- newEvent.initKeyEvent(event.type, event.canBubble, event.cancelable, event.view, false, false, false, false, 255, ch[i]);
- event.target.dispatchEvent(newEvent);
- }
- } else {
- var newEvent = document.createEvent("KeyEvents");
- newEvent.initKeyEvent(event.type, event.canBubble, event.cancelable, event.view, false, false, false, false, 255, ch);
- event.target.dispatchEvent(newEvent);
- }
- }
- },
-
- // transformer functions
-
- getSelection : function(node) {
- if (node==null) {
- // retrieve global selection
- var window = document.commandDispatcher.focusedWindow;
- if (window && window.getSelection) {
- return window.getSelection();
- }
- } else {
- // return selection from editor or from midas content window
- return node.getSelection ? node.getSelection() : node.contentWindow.getSelection();
- }
- return null;
- },
-
- splitHtmlString: function(string) {
- var re = /<[\/]?[!A-Z][^>]*>/ig;
- var result = new Array();
- var lastIndex = 0;
- var arr = null;
- while ( (arr = re.exec(string)) != null) {
- result[result.length] = string.substring(lastIndex, arr.index);
- result[result.length] = string.substring(arr.index, re.lastIndex);
- lastIndex = re.lastIndex;
- }
- result[result.length] = string.substr(lastIndex);
- return result;
- },
-
- convertWithHTML: function(src, skipHtml, converter) {
- if (src == "" || src == null) return src;
- if (!skipHtml) {
- return converter(src);
- } else {
- var arr = TextEditorLib.splitHtmlString(src);
- for (var i = 0; i < arr.length; i++) {
- if ( (i % 2) == 0) arr[i] = converter(arr[i]);
- }
- return arr.join("");
- }
- },
-
- insertNodeAtSelection : function(selection, insertNode)
- {
- var range = selection.getRangeAt(0);
- selection.removeAllRanges();
- range.deleteContents();
- var container = range.startContainer;
- var pos = range.startOffset;
- if (!pos) {
- var newNode = document.createTextNode(" ");
- range.insertNode(newNode);
- selection.addRange(range);
- range = selection.getRangeAt(0);
- selection.removeAllRanges();
- range.deleteContents();
- container = range.startContainer;
- pos = range.startOffset;
- }
- range=document.createRange();
- // insertion logic
- // special case inserting text into text node
- if (container.nodeType==insertNode.TEXT_NODE && insertNode.nodeType==insertNode.TEXT_NODE) {
- container.insertData(pos, insertNode.nodeValue);
- // set cursor position at the end
- range.setEnd(container, pos+insertNode.length);
- range.setStart(container, pos+insertNode.length);
- } else {
- var afterNode;
- if (container.nodeType==insertNode.TEXT_NODE) {
- // inserting into a textnode, create 2 new nodes
- // insert the new node inbetween
- var textNode = container;
- container = textNode.parentNode;
- var text = textNode.nodeValue;
- // text before the split
- var textBefore = text.substr(0,pos);
- // text after the split
- var textAfter = text.substr(pos);
- var beforeNode = document.createTextNode(textBefore);
- afterNode = document.createTextNode(textAfter);
- // insert the all new nodes before the old one
- container.insertBefore(afterNode, textNode);
- container.insertBefore(insertNode, afterNode);
- container.insertBefore(beforeNode, insertNode);
- // remove the old node
- container.removeChild(textNode);
- } else {
- // else simply insert the node
- afterNode = container.childNodes[pos];
- container.insertBefore(insertNode, afterNode);
- }
- // set cursor
- range.setEnd(afterNode, 0);
- range.setStart(afterNode, 0);
- }
- selection.addRange(range);
- },
-
- isTransformableNodeType : function(node) {
- return node.nodeType == node.TEXT_NODE || node.nodeType == node.PROCESSING_INSTRUCTION_NODE || node.nodeType == node.COMMENT_NODE;
- },
-
- isBeginningOfTransformableContainer : function(range, node, state) {
- var startContainer = range.startContainer;
- if (!state.started &&
- ((TextEditorLib.isTransformableNodeType(startContainer) && node == state.range.startContainer) ||
- (startContainer.childNodes && node == startContainer.childNodes[range.startOffset]))) {
- return true;
- }
- return false;
- },
-
- isEndOfTransformableContainer : function(range, node, state) {
- var endContainer = range.endContainer;
- if (!state.finished &&
- ((TextEditorLib.isTransformableNodeType(endContainer) && node == endContainer) || ((endContainer.childNodes.length > 0) &&
- node == endContainer.childNodes[state.range.endOffset - 1]))) {
- return true;
- }
- return false;
- },
-
- NodeRangeConversionState : function(state, node) {
- this.node = node;
- this.range = state.range;
- this.convert = state.convert;
- this.isAsynchronous = state.isAsynchronous;
- this.started = state.started;
- this.finished = state.finished;
- this.converted = state.converted;
- this.toString = function() {
- return "started : " + this.started + ", finished: " + this.finished;
- };
- },
-
- transformNodeText : function(node, state) {
- var start = (node == state.range.startContainer) ? state.range.startOffset : 0;
- var end = (node == state.range.endContainer) ? state.range.endOffset : node.nodeValue.length;
- var remainder = (node == state.range.endContainer) ? node.nodeValue.length - state.range.endOffset : 0;
- var convertedValue;
- if (state.isAsynchronous) {
- var stateCopy = new TextEditorLib.NodeRangeConversionState(state, node);
- // call asynchronous conversion function, pass it the text to be converted and
- // copy of the state, so that the conversion function can initiate conversion, return
- // and at the end of conversion make a call back to this function with the copied state
- // in synchronous mode.
- var conversionResult = state.convert(node.nodeValue.substring(start, end), stateCopy);
- // if conversionResult is not null, then the call was resolved synchronously (from cache,)
- // and there will be no asynchronous call back.
- if (conversionResult!=null) {
- convertedValue = node.nodeValue.substring(0, start) + conversionResult + node.nodeValue.substr(end);
- }
- } else {
- // call synchronous conversion function.
- convertedValue = node.nodeValue.substring(0, start) + state.convert(node.nodeValue.substring(start, end)) + node.nodeValue.substr(end);
- }
- state.converted = true;
- if (!TextEditorLib.isObjectEmpty(convertedValue)) {
- node.nodeValue = convertedValue;
- if (node == state.range.endContainer) {
- state.range.setEnd(node, node.nodeValue.length - remainder);
- }
- if (node == state.range.startContainer) {
- state.range.setStart(node, start);
- }
- }
- },
-
- RangeConversionState: function(range, converter, isAsynchronous) {
- this.range = range;
- this.convert = converter;
- this.isAsynchronous = isAsynchronous;
- this.started = false;
- this.finished = false;
- this.converted = false;
- this.toString = function() {
- return "started : " + this.started + ", finished: " + this.finished;
- };
- },
-
- transformRangeNode : function(node, state) {
- if (state.started && state.finished) {
- return;
- }
- if (TextEditorLib.isBeginningOfTransformableContainer(state.range, node, state)) {
- state.started = true;
- }
- if (TextEditorLib.isTransformableNodeType(node)) {
- if (state.started && !state.finished) {
- TextEditorLib.transformNodeText(node, state);
- }
- } else if (node.childNodes) {
- for (var i = 0; i < node.childNodes.length; i++) {
- TextEditorLib.transformRangeNode(node.childNodes[i], state);
- if (state.started && state.finished)
- break;
- }
- }
- if (TextEditorLib.isEndOfTransformableContainer(state.range, node, state)) {
- state.finished = true;
- }
- },
-
- transformHTMLSelection : function(selection, transformerFunc, isAsynchronous) {
- if (selection==null || TextEditorLib.isObjectEmpty(transformerFunc)) {
- return;
- }
- for (var i=0;i<selection.rangeCount;i++) {
- var conversionState = new TextEditorLib.RangeConversionState(selection.getRangeAt(i), transformerFunc, isAsynchronous);
- TextEditorLib.transformRangeNode(selection.getRangeAt(i).commonAncestorContainer, conversionState);
- if (!conversionState.converted && selection.getRangeAt(i).toString().length>0) {
- // extra conversion handling
- var range = selection.getRangeAt(i);
- var newNode = document.createTextNode(transformerFunc(range.toString()));
- TextEditorLib.insertNodeAtSelection(selection, newNode);
- }
- }
-
- },
-
- transformSelectionText : function(transformerFunc, skipHtml, isAsynchronous) {
- var node = TextEditorLib.getNode();
- if (node!=null || !TextEditorLib.isObjectEmpty(TextEditorLib.getSelection(null))) {
- if (node==null || TextEditorLib.isNodeEditor(node)) {
- // transform text in an HTML document or HTML editor
- TextEditorLib.transformHTMLSelection(TextEditorLib.getSelection(node), transformerFunc, isAsynchronous);
- } else if (node!=null) {
- // transform text in a text input field or textarea
- var oldValue = node.value;
- var selStart = node.selectionStart;
- var selEnd = node.selectionEnd;
- var value = oldValue.substring(selStart, selEnd);
- var newValue = TextEditorLib.convertWithHTML(value, skipHtml, transformerFunc);
- var scrollTop = node.scrollTop;
- node.value = oldValue.substring(0, selStart) + newValue + oldValue.substring(selEnd, oldValue.length);
- node.selectionStart = selStart + value.length;
- node.selectionEnd = selStart + value.length;
- }
- }
- }
-
- }
-
- /**
- * TextNode class is used to hold reference to a text editor field.
- * TextNode can initialize itself from a document node, it handles
- * event listeners on the editor field.
- */
-
- var TextNode = {
- //txtnode : undefined,
- //eventToListen : undefined,
- //translationFunction : undefined,
-
- TextNode : function(node, onKeyEventToListen, onKeyHandlerFunc, translationFunc, onCapsLockEventToListen, onCapsLockHandlerFunc, isNodeEditor) {
- this.txtnode = node;
- this.eventToListen = onKeyEventToListen;
- this.onKeyHandlerFunc = onKeyHandlerFunc;
- this.translationFunction = translationFunc;
- this.onCapsLockEventToListen = onCapsLockEventToListen;
- this.onCapsLockHandlerFunc = onCapsLockHandlerFunc;
- this.isNodeEditor = isNodeEditor;
- this.borderStyle = null;
- this.backgroundColor = null;
- // attach object's methods
- this.getNode = TextNode.getNode;
- this.setUp = TextNode.setUp;
- this.tearDown = TextNode.tearDown;
- this.attachListener = TextNode.attachListener;
- this.detachListener = TextNode.detachListener;
- },
-
- getTranslationFunction : function() {
- return this.translationFunction;
- },
-
- getNode : function() {
- return this.txtnode;
- },
-
- setUp : function() {
- this.attachListener(this.eventToListen, this.onKeyHandlerFunc);
- this.attachListener(this.onCapsLockEventToListen, this.onCapsLockHandlerFunc);
- // set some stupid style parameters.
- var style = "dashed 1px red";
- if (this.isNodeEditor) {
- this.borderStyle = this.txtnode.style.border;
- this.txtnode.style.border = style;
- } else {
- this.borderStyle = this.txtnode.style.outline;
- this.txtnode.style.outline = style;
- }
- this.backgroundColor = document.defaultView.getComputedStyle(this.txtnode, "").getPropertyValue("background-color");
- var newRGB = getModifiedColor(this.backgroundColor, -15,0,-15);
- this.txtnode.style.backgroundColor=newRGB;
- },
-
- tearDown : function() {
- this.detachListener(this.eventToListen, this.onKeyHandlerFunc);
- this.detachListener(this.onCapsLockEventToListen, this.onCapsLockHandlerFunc);
- // unset some stupid style parameters.
- this.txtnode.style.backgroundColor=this.backgroundColor;
- if (this.isNodeEditor) {
- this.txtnode.style.border = this.borderStyle;
- } else {
- this.txtnode.style.outline = this.borderStyle;
- }
- },
-
- // attaches a listener to the txtnode, registers func to process, false - for bubbling
- attachListener : function(eventToListen, onKeyHandlerFunc) {
- if (eventToListen == undefined || onKeyHandlerFunc == undefined) {
- throw "Cannot attach listener.";
- }
- if (this.txtnode.contentDocument) {
- this.txtnode.contentDocument.documentElement.addEventListener(eventToListen, onKeyHandlerFunc, false);
- } else {
- this.txtnode.addEventListener(eventToListen, onKeyHandlerFunc, false);
- }
- },
-
- // detaches previously attached listener from the txtnode
- detachListener : function(eventToListen, onKeyHandlerFunc) {
- if (eventToListen == undefined || onKeyHandlerFunc == undefined) {
- throw "Cannot detach listener.";
- }
- if (this.txtnode.contentDocument) {
- this.txtnode.contentDocument.documentElement.removeEventListener(eventToListen, onKeyHandlerFunc, false);
- } else {
- this.txtnode.removeEventListener(eventToListen, onKeyHandlerFunc, false);
- }
- }
- }